package org.walkerljl.commons.trace;

import org.walkerljl.commons.config.Configurator;
import org.walkerljl.commons.config.ConfiguratorFactory;
import org.walkerljl.commons.exception.AppException;
import org.walkerljl.commons.log.Logger;
import org.walkerljl.commons.log.LoggerFactory;
import org.walkerljl.commons.util.JSONUtils;
import org.walkerljl.commons.util.StringUtils;

import java.util.Set;

/**
 * Tracer
 *
 * @author lijunlin
 * @Date 2016/11/25
 */
public class Tracer {

    private static final Logger LOGGER = LoggerFactory.getLogger(Tracer.class);

    private static String appCode;
    private static MonitorProvider monitorProvider;
    private static AlarmProvider alarmProvider;

    private Level level;
    private InvocationInfo invocationInfo;
    private boolean handleExceptionQuietly;
    private Object monitoringInfo;

    /**
     * 绑定应用编码
     *
     * @param appCode
     */
    public void bindAppCode(String appCode) {
        Tracer.appCode = appCode;
    }

    /**
     * 绑定监控器
     *
     * @param monitorProvider
     */
    public static void bindMonitorProvider(MonitorProvider monitorProvider) {
        Tracer.monitorProvider = monitorProvider;
    }

    /**
     * 绑定报警器
     *
     * @param alarmProvider
     */
    public static void bindAlarmProvider(AlarmProvider alarmProvider) {
        Tracer.alarmProvider = alarmProvider;
    }

    /**
     * 开始
     *
     * @param objectClass 监控对象Class
     * @param methodName  监控对象方法名称
     * @param request     请求对象
     * @return
     */
    public Tracer begin(Class<?> objectClass, String methodName, Object... request) {
        return this.begin(objectClass, methodName, Level.INFO, true, null, request);
    }

    /**
     * 开始
     *
     * @param objectClass 监控对象Class
     * @param methodName  监控对象方法名称
     * @param description 描述
     * @param request     请求对象
     * @return
     */
    public Tracer begin(Class<?> objectClass, String methodName, String description, Object... request) {
        return this.begin(objectClass, methodName, Level.INFO, description, request);
    }

    /**
     * 开始
     *
     * @param objectClass 监控对象Class
     * @param methodName  监控对象方法名称
     * @param level       监控等级
     * @param request     请求对象
     * @return
     */
    public Tracer begin(Class<?> objectClass, String methodName, Level level, Object... request) {
        return this.begin(objectClass, methodName, level, null, request);
    }

    /**
     * 开始
     *
     * @param objectClass 监控对象Class
     * @param methodName  监控对象方法名称
     * @param level       监控等级
     * @param description 描述
     * @param request     请求对象
     * @return
     */
    public Tracer begin(Class<?> objectClass, String methodName, Level level, String description, Object... request) {
        try {
            this.level = level;
            this.invocationInfo = new InvocationInfo(objectClass, methodName, description, request);
            if (monitorProvider != null && isDoMonitoring()) {
                String alarmKey = buildAlarmKey();
                try {
                    this.monitoringInfo = monitorProvider.begin(appCode, alarmKey);
                    if (LOGGER.isDebugEnabled()) {
                        LOGGER.debug(String.format("Do monitoring:%s.", alarmKey));
                    }
                } catch (Throwable e) {
                    LOGGER.error(String.format("Do monitoring error:%s.", alarmKey));
                }
            }
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(String.format("Begin->%s.", buildTraceInfo()));
            }
        } catch (Throwable e) {
            LOGGER.error(e);
        }
        return this;
    }

    /**
     * 是否进行监控
     *
     * @return
     */
    private boolean isDoMonitoring() {
        Configurator configurator = ConfiguratorFactory.getInstance().getStdConfigurator();
        Set<String> methodNames = configurator.getAsSet(String.class,
                invocationInfo.getObjectClass().getName(), ",");
        return methodNames != null &&
                (methodNames.contains(invocationInfo.getMethodName())
                        || methodNames.contains("*"));
    }

    /**
     * 标注异常
     *
     * @param throwable 异常对象
     * @return
     */
    public Tracer markException(Throwable throwable) {
        return this.markException(throwable, false);
    }

    /**
     * 标注异常
     *
     * @param throwable              异常对象
     * @param handleExceptionQuietly 静默处理异常，true：静默处理，false：重新抛出异常
     * @return
     */
    public Tracer markException(Throwable throwable, boolean handleExceptionQuietly) {
        try {
            this.level = Level.ERROR;
            invocationInfo.setFailure(throwable);
            this.handleExceptionQuietly = handleExceptionQuietly;
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(String.format("Mark exception->%s.", buildTraceInfo()));
            }
        } catch (Throwable e) {
            LOGGER.error(e);
        }
        return this;
    }

    /**
     * 响应数据
     */
    public void trace() {
        this.trace(null);
    }

    /**
     * 跟踪
     *
     * @param response 响应数据
     */
    public void trace(Object response) {
        try {
            this.invocationInfo.setResponse(response);

            //do monitor
            if (monitoringInfo != null && monitorProvider != null) {
                if (level == Level.ERROR) {
                    monitorProvider.markException(monitoringInfo);
                    if (LOGGER.isDebugEnabled()) {
                        LOGGER.debug("Mark exception by monitor provider.");
                    }
                }
                monitorProvider.end(monitoringInfo);
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Do end by monitor provider.");
                }
            }
        } catch (Throwable e) {
            LOGGER.error(e);
        }

        Logger logger = getLogger();
        //record log
        if (level == null || level == Level.DEBUG) {
            if (logger.isDebugEnabled()) {
                logger.debug(buildTraceMessage());
            }
        } else if (level == Level.INFO) {
            if (logger.isInfoEnabled()) {
                logger.info(buildTraceMessage());
            }
        } else if (level == Level.WARN) {
            if (logger.isWarnEnabled()) {
                logger.warn(buildTraceMessage());
            }
        } else if (level == Level.ERROR) {
            if (handleExceptionQuietly) {
                traceExceptionQuietly();
            } else {
                traceException();
            }
        }
    }

    /**
     * 跟踪异常
     */
    private void traceException() {
        Throwable e = invocationInfo.getThrowable();
        doTraceException();
        //rethrow the exception
        if (e instanceof RuntimeException) {
            throw (RuntimeException) e;
        } else {
            throw new Error(e.getMessage(), e);
        }
    }

    /**
     * 静默方式跟踪异常
     */
    private void traceExceptionQuietly() {
        doTraceException();
    }

    /**
     * Do trace exceptiojn
     */
    private void doTraceException() {
        Logger logger = getLogger();
        Throwable e = invocationInfo.getThrowable();
        String alarmKey = buildAlarmKey();
        try {
            if (e != null && e instanceof AppException) {
                logger.warn(buildTraceMessage());
            } else {
                String message = buildTraceMessage();
                logger.error(message, e);
                if (alarmProvider != null) {
                    if (StringUtils.isEmpty(alarmKey)) {
                        alarmProvider.alarm(message);
                    } else {
                        alarmProvider.alarm(alarmKey, message);
                    }
                }
            }
        } catch (Throwable inner) {
            logger.error("Fail to trace exception:" + inner.getMessage(), inner);
        }
    }

    /**
     * 构建跟踪信息
     *
     * @return
     */
    private String buildTraceInfo() {
        return String.format("level=%s,invocationInfo=%s,handleExceptionQuietly=%s", level, JSONUtils.toJSONString(invocationInfo), handleExceptionQuietly);
    }

    /**
     * 构建跟踪消息
     *
     * @return
     */
    private String buildTraceMessage() {
        String messagePrefix = (invocationInfo.getDescription() == null || "".equals(invocationInfo.getDescription().trim())
                ? buildAlarmKey() : invocationInfo.getDescription().trim());
        String requestString = "";
        try {
            requestString = JSONUtils.toJSONString(invocationInfo.getRequest());
        } catch (Throwable e) {
            LOGGER.error(e);
        }
        String responseString = null;
        if (invocationInfo.getThrowable() == null) {
            try {
                responseString = JSONUtils.toJSONString(invocationInfo.getResponse());
            } catch (Throwable e) {
                LOGGER.error(e);
            }
        } else {
            responseString = invocationInfo.getThrowable().getMessage();
        }
        return String.format("%s:Response->%s,Request->%s.", messagePrefix, responseString, requestString);
    }

    /**
     * 构建报警Key
     *
     * @return
     */
    private String buildAlarmKey() {
        String prefix = ConfiguratorFactory.getIns().getStdConfigurator().getAsString("alias." + invocationInfo.getObjectClass().getName(),
                invocationInfo.getObjectClass().getName());
        return prefix + "." + invocationInfo.getMethodName();
    }

    /**
     * 获取日志对象
     *
     * @return
     */
    private Logger getLogger() {
        return LoggerFactory.getLogger(invocationInfo.getObjectClass());
    }

    /**
     * 等级
     */
    public enum Level {

        /**
         * DEBUG
         */
        DEBUG,

        /**
         * INFO
         */
        INFO,

        /**
         * WARN
         */
        WARN,

        /**
         * ERROR
         */
        ERROR;
    }
}